Skip to content

Differences from HTML

JSX looks like HTML, but there are some fundamental differences. In this lesson, we'll dig into those differences.

Note: Please don't feel like you have to memorize all these rules right now! Fortunately, React is pretty good about guiding us in the right direction when we make these mistakes. If you keep an eye on the developer console, you don't need to memorize these rules.

Reserved words

JavaScript has a couple dozen "reserved words". Reserved words are keywords with built-in functionality. Because they do something already, we can't use them in our JSX.

For example:

const while = 10;

If we run this code, we'll get a syntax error, because while is a reserved word. It's used for “while loops” like this:

let count = 5;
while (count > 0) {
console.log('Countdown:', count);
count -= 1;
}

Because JSX gets transformed into JS, we can't use any reserved words in our JSX. And this is a problem, because HTML attributes sometimes overlap with JavaScript reserved words.

Consider this JSX:

const element = (
<div>
<label for="name">
Name:
</label>
<input
id="name"
class="fun-input"
/>
</div>
);

If we compile this into JavaScript, we'll discover that we're using two reserved words:

  • for
  • class

To work around this conflict, React uses slight variations on these two terms:

const element = (
<div>
<label htmlFor="name">
Name:
</label>
<input
id="name"
className="fun-input"
/>
</div>
);

Specifically:

  • for is changed to htmlFor
  • class is changed to className

It's a bummer that we need to do this mental conversion, but it doesn't take too long to adjust!

Self-closing tags

HTML is a pretty loosey-goosey language. For example, this is perfectly valid HTML:

<div>
<p>This paragraph is opened… but never closed.
<p>We're omitting the closing tags!
</div>

Paragraph tags can't be nested. The browser is smart enough to figure out that the first paragraph must end before the second paragraph starts, and it will automatically insert the </p> for you, similar to how the JavaScript engine can insert missing semi-colons for you.

JSX is a bit of a wet blanket. We absolutely need to close every tag we open:

const element = (
<div>
<p>These paragraphs are valid.</p>
<p>They include the closing tags.</p>
</div>
);

In HTML5, certain elements don't have closing tags. For example, the img tag can't have children, and so it doesn't need to be closed:

<img
alt="A friendly kitten"
src="/images/kitten.jpg"
>

Our JSX compiler won't like this one bit. We need to explicitly close this tag. We can do this with a "self-closing" tag:

const element = (
<img
alt="A friendly kitten"
src="/images/kitten.jpg"
/>
);

(This self-closing syntax, <img />, comes from earlier versions of HTML. It isn't necessary in modern HTML, but it's still valid, and many developers continue to use it out of habit.)

Case-sensitive tags

HTML is a case-insensitive language. In fact, it was common many years ago for HTML to be written in all-uppercase:

<MAIN>
<HEADER>
<H1>Hello World!</H1>
</HEADER>
<P>
This HTML is so loud!
</P>
</MAIN>

JSX, by contrast, is case-sensitive. Our tags must all be lowercase:

const element = (
<main>
<header>
<h1>Hello World!</h1>
</header>
<p>
This HTML is so loud!
</p>
</main>
);

This restriction might seem arbitrary, but there's a very good reason for it: the JSX compiler uses the tag's case to tell whether it's a "primitive" (part of the DOM) or a custom component. We'll learn more about this when we learn about components.

Case-sensitive attributes

In JSX, our attributes need to be .

For example, this is valid HTML:

<video
src="/videos/cat-skateboarding.mp4"
autoplay="true"
>

In JSX, we need to capitalize the “p” in “autoplay”, since “auto” and “play” are distinct words:

const element = (
<video
src="/videos/cat-skateboarding.mp4"
autoPlay={true}
// ^ Capital “P”
/>
);

(I've also switched to use an expression slot, {true}, instead of keeping it as a string. This is slightly more idiomatic in React, although both options will work.)

It can be hard to tell whether an attribute contains multiple words or not, especially if English isn't your first language! Fortunately, you'll get a helpful warning in the developer tools if you make a mistake.

Code Playground

Open in CodeSandbox
import React from 'react';
import { createRoot } from 'react-dom/client';

const element = (
<video
src="/videos/cat-skateboarding.mp4"
autoplay={true}
/>
);

const container = document.querySelector('#root');
const root = createRoot(container);
root.render(element);
preview
console
  1. Warning: Invalid DOM property `autoplay`. Did you mean `autoPlay`? at video

Other properties that need to be "camelCased" include:

  • onclickonClick
  • tabindextabIndex
  • stroke-dasharraystrokeDasharray (this one is specific for SVGs)

Inline styles

In HTML, the style attribute allows us to apply some styles inline, to a specified element:

<h1 style="font-size: 2rem;">
Hello World!
</h1>

In JSX, style instead takes an object:

const element = (
<h1 style={{ fontSize: '2rem' }}>
Hello World!
</h1>
);

All CSS properties are written in “camelCase”. Every dash is replaced by capitalizing the subsequent word:

  • background-position becomes backgroundPosition
  • border-bottom-color becomes borderBottomColor

For vendor prefixes like -webkit-font-smoothing, we capitalize the first letter as well: WebkitFontSmoothing.

Also, React will automatically apply the px suffix for certain CSS properties. For example:

<div
style={{
width: 200, // Equivalent to `width: 200px`
paddingTop: 8, // Equivalent to `padding-top: 8px`
}}
>

Watch out for properties that take a unitless value by default, like flex or lineHeight.

For example, this code will produce lines that are twenty times taller than default, not lines that are 20px tall:

<p
style={{
lineHeight: 20, // Equivalent to `line-height: 20`
}}
>

While it's a common convention in React to use unitless values where possible, you can absolutely use full units if you prefer!

<p
style={{
width: '200px',
paddingTop: '8px',
}}
>